home *** CD-ROM | disk | FTP | other *** search
Text File | 1992-09-08 | 24.3 KB | 665 lines | [TEXT/CCL2] |
- ;;;
- ;;; outline-window.lisp
- ;;;
-
- #|
- ================================================================
- Purpose ========================================================
- ================================================================
- Defines a Finder-like hierarchy display. Outline-window instances display
- hierarchies.
-
-
- ==== To use this file ====
- (Note: Generic functions specify the interface.)
-
- This file defines the outline-node and outline-window classes which enable
- viewing hierarchies. See the examples at the bottom for their use.
-
-
- ================================================================
- Status =========================================================
- ================================================================
- Usable.
-
- To Do:
- - Convert outline-window to outline-fred-mixin (use
- :scrolling-fred-dialog-item) !
- - Test with circular and repeating objects.
-
- Bugs:
- * Define (setf on-root-outline-window) :after to update the display.
- - Doesn't remember whether a node was previously expanded when re-expanding it.
- √- Expanding or collapsing a selected node causes part of the node's line to
- be selected.
- √- Collapsing a node that contains expanded children doesn't collapse
- children.
- √* Clicking in scroll bar doesn't scroll. Will go away when I switch to
- dialog-items.
-
- ================================================================
- Change history =================================================
- ================================================================
- 25-Aug-92 mc Created.
- 07-Sep-92 mc Finished and started testing.
-
- |#
-
-
- (in-package "CCL")
-
- (export '(OUTLINE-NODE
- OBJECT-OUTLINE-NODE
- F-SORT-CHILDREN
- OUTLINE-WINDOW
- F-OUTLINE-NODE-SELECTED-OW
- ON-ROOT-OUTLINE-WINDOW
- EXPAND-OUTLINE-NODE-OW
- COLLAPSE-OUTLINE-NODE-OW)
- "CCL")
-
-
- (require "F-PT-IN-SCROLL" "CCL:UMASS Utils;f-pt-in-scroll")
-
-
- ;;;================================================================
- ;;; Define the outline-node class and methods.
- ;;;================================================================
-
- (defclass outline-node ()
- ((object
- :accessor object-outline-node
- :initarg :object
- :type t
- :documentation "The object this node displays.")
- (int-level
- :accessor int-level-outline-node
- :initarg :int-level
- :type fixnum
- :documentation "This node's level. Zero is the root.")
- (on-parent
- :accessor on-parent-outline-node
- :initarg :on-parent
- :initform nil
- :type outline-node
- :documentation "This node's parent node. Multiple inheritance is not
- supported.")
- (l-on-children
- :accessor l-on-children-outline-node
- :initarg :l-on-children
- :initform ()
- :type list
- :documentation "This node's children nodes. Children are computed and
- saved as necessary, i.e. when the node is expanded.")
- (f-children-computed
- :accessor f-children-computed-outline-node
- :initarg :f-children-computed
- :initform nil
- :type t
- :documentation "A flag that is non-nil if this node's children are
- cached in l-on-children-outline-node .")
- (f-expanded
- :accessor f-expanded-outline-node
- :initarg :f-expanded
- :initform nil
- :type t
- :documentation "A flag that is non-nil if this node's children are
- displayed.")
- ;;
- (mark-line-start
- :accessor mark-line-start-outline-node
- :initarg :mark-line-start
- :type buffer-mark
- :documentation "A FRED buffer mark in an outline-window's fred-buffer
- that marks the start of this object's line.")
- )
- (:documentation "A class that holds objects in a hierarchy for the
- outline view."))
-
-
- (defgeneric object-outline-node (outline-node)
- (:documentation "Returns the object outline-node represents."))
-
-
- (defmethod initialize-instance :before ((outline-node outline-node)
- &key object)
- (declare (optimize speed))
- ;;
- (check-type object (not null)))
-
-
- (defmethod initialize-instance :after ((outline-node outline-node) &key)
- "Sets outline-node's int-level-outline-node to 1+ its parent's, if
- non-nil. Otherwise sets it to zero."
- (declare (optimize speed))
- ;;
- (setf (int-level-outline-node outline-node)
- (if (on-parent-outline-node outline-node)
- (1+ (int-level-outline-node (on-parent-outline-node outline-node)))
- 0)))
-
-
- (defmethod compute-children-if-necessary ((outline-node outline-node)
- (fn-l-children-outline-window function))
- "Computes outline-node's children if necessary, setting their parents to
- outline-node. Fn-l-children-outline-window is a function that takes an
- object as its only argument and returns a list of objects that are the
- passed object's children."
- (declare (optimize speed))
- ;;
- (unless (f-children-computed-outline-node outline-node)
- (setf (l-on-children-outline-node outline-node)
- (loop for object-child in (funcall fn-l-children-outline-window
- (object-outline-node outline-node))
- for object-node-child = (make-instance 'outline-node
- :object object-child
- :on-parent outline-node)
- collect object-node-child)
- (f-children-computed-outline-node outline-node) t)))
-
-
- (defmethod expand-outline-node ((outline-node outline-node)
- (fn-l-children-outline-window function))
- "Calls compute-children-if-necessary then sets f-expanded-outline-node to t."
- (declare (optimize speed))
- ;;
- (compute-children-if-necessary outline-node fn-l-children-outline-window)
- (setf (f-expanded-outline-node outline-node) t))
-
-
- (defmethod collapse-outline-node ((outline-node outline-node))
- "Sets f-expanded-outline-node to nil."
- (declare (optimize speed))
- ;;
- (setf (f-expanded-outline-node outline-node) nil))
-
-
- ;;;================================================================
- ;;; Define the outline-window class and methods.
- ;;;================================================================
-
- (defclass outline-window (fred-window)
- ((on-root
- :accessor on-root-outline-window
- :initarg :on-root
- :type outline-node
- :documentation "The root object's node of this window.")
- (int-indent
- :accessor int-indent-outline-window
- :initarg :int-indent
- :initform 5
- :type fixnum
- :documentation "The number of characters to indent each level.")
- (fn-l-children
- :accessor fn-l-children-outline-window
- :initarg :fn-l-children
- :type function
- :documentation "A function of one argument (an object) that returns a
- list of children objects. Used to expand nodes.")
- (fn-str-object
- :accessor fn-str-object-outline-window
- :initarg :fn-str-object
- :type function
- :documentation "A function of one argument (an object) that returns a
- string representing the object. The default is #'str-format-object .")
- (outline-node-selected
- :accessor f-outline-node-selected-ow
- :type (or null outline-node)
- :initform nil
- :documentation "The outline node currently selected or nil if none is.")
- (l-outline-node
- :accessor l-outline-node-ow
- :initarg :l-outline-node
- :initform ()
- :type list
- :documentation "A list of all displayed outline-nodes. Used by
- object-node-from-int-line-start .")
- (f-sort-children
- :accessor f-sort-children-outline-window
- :initarg :f-sort-children
- :initform t
- :type t
- :documentation "A flag that, when non-nil, causes
- expand-outline-node-ow to sort the expanded children.")
- )
- (:documentation "A class that holds objects in a hierarchy for the
- outline view.")
- (:default-initargs
- :window-title "Outline"
- :view-font '("Monaco" 9)))
-
-
- (defmethod initialize-instance ((outline-window outline-window)
- &rest plist-init-args
- &key root-object fn-l-children fn-str-object)
- (declare (optimize speed))
- ;;
- (check-type root-object (not null))
- (check-type fn-l-children function)
- ;;
- ;; On-root's buffer is set when the object's text is inserted:
- ;;
- (let ((on-root (make-instance 'outline-node :object root-object :on-parent nil)))
- (apply #'call-next-method ;do the usual
- outline-window
- :on-root on-root
- :l-outline-node (list on-root)
- :fn-str-object (or fn-str-object #'str-format-object)
- plist-init-args)
- ;;
- (let ((buffer (fred-buffer outline-window)))
- (ccl::%buffer-set-read-only buffer t)
- (fred-update outline-window) ;shows the ® in the title
- ))
- ;;
- ;; Without-interrupts keeps clicks from happening during expansion, which
- ;; causes "> Error: Slot MARK-LINE-START is unbound in #<OUTLINE-NODE #xC259F1>"
- ;;
- (without-interrupts
- (insert-text-outline-node-ow (on-root-outline-window outline-window)
- outline-window 0)
- (expand-outline-node-ow (on-root-outline-window outline-window)
- outline-window)))
-
-
- ;;;================================================================
- ;;; Define mcl methods.
- ;;;================================================================
-
- (defmethod window-can-do-operation ((outline-window outline-window)
- sym-op &optional menu-item)
- "Returns nil for all operations, which makes the Edit menu items inactive."
- (declare (optimize speed)
- (ignore sym-op menu-item))
- ;;
- nil)
-
-
- (defmethod view-default-position ((outline-window outline-window))
- #@(3 41))
-
-
- (defmethod view-default-size ((outline-window outline-window))
- #@(212 180))
-
-
- (defmethod view-key-event-handler ((outline-window outline-window)
- (character character))
- "Disallows cursor movement, because such movement deselects but doesn't
- update the selected object."
- ;;
- ;; Fix: Allow arrow characters to do expand, contract, move up, down, etc.
- ;;
- (ed-beep))
-
-
- (defmethod window-needs-saving-p ((outline-window outline-window))
- ;;
- nil)
-
-
- (defmethod window-update-cursor ((outline-window outline-window)
- (pt-where integer))
- "Changes the cursor: If pt-where is above an outline arrow then makes it
- *plus-cursor*; otherwise makes it *arrow-cursor* ."
- (declare (ignore pt-where))
- ;;
- ;; Fix!
- ;;
- (set-cursor *arrow-cursor*))
-
-
- (defmethod view-click-event-handler ((outline-window outline-window)
- (pt-where fixnum))
- "Selects the line corresponding to pt-where."
- (declare (optimize speed))
- ;;
- (if (f-pt-in-scroll outline-window pt-where)
- (call-next-method)
- (do-outline-click outline-window pt-where)))
-
-
- (defmethod do-outline-click ((outline-window outline-window)
- (pt-where fixnum))
- (declare (optimize speed))
- ;;
- (let* ((buffer (fred-buffer outline-window))
- (int-buffer-pos-click (fred-point-position outline-window pt-where))
- (int-line-start (buffer-line-start buffer int-buffer-pos-click))
- (int-line-end (buffer-line-end buffer int-buffer-pos-click))
- (clicked-on-object-node (object-node-from-int-line-start
- outline-window int-line-start))
- (f-object-node-arrow (f-on-arrow-pt outline-window pt-where)))
- ;;
- ;; If they clicked on an arrow then track until they release and do the
- ;; arrow operation only if they released on the arrow (in addition to
- ;; having clicked on it). This is proper Mac interface style.
- ;;
- (when f-object-node-arrow
- (loop ;do (track-mouse-above-arrow f-object-node-arrow outline-window)
- while (#_StillDown)
- finally (let ((f-object-node-arrow-new
- (f-on-arrow-pt outline-window
- (view-mouse-position outline-window))))
- (unless (eq f-object-node-arrow-new
- f-object-node-arrow)
- (setf f-object-node-arrow nil)))))
- ;;
- (cond ((and f-object-node-arrow
- (f-expanded-outline-node f-object-node-arrow))
- (collapse-outline-node-ow f-object-node-arrow outline-window)
- (collapse-selection outline-window t)
- (setf (f-outline-node-selected-ow outline-window) nil))
- ((and f-object-node-arrow
- (not (f-expanded-outline-node f-object-node-arrow)))
- (expand-outline-node-ow f-object-node-arrow outline-window)
- (collapse-selection outline-window t)
- (setf (f-outline-node-selected-ow outline-window) nil))
- (clicked-on-object-node
- (set-selection-range outline-window int-line-start int-line-end)
- (setf (f-outline-node-selected-ow outline-window)
- clicked-on-object-node))
- (t
- (collapse-selection outline-window t)
- (setf (f-outline-node-selected-ow outline-window) nil)))
- (fred-update outline-window)))
-
-
- ;;;================================================================
- ;;; Define methods for selected objects.
- ;;;================================================================
-
- (defgeneric f-outline-node-selected-ow (outline-window)
- (:documentation "Returns the object currently selected in outline-window
- or nil if none are selected."))
-
-
- ;;;================================================================
- ;;; Define functions for expanding and collapsing nodes.
- ;;;================================================================
-
- (defgeneric expand-outline-node-ow (outline-node outline-window)
- (:documentation "Expands outline-node in outline-window, and updates the
- display."))
-
-
- (defmethod expand-outline-node-ow ((outline-node outline-node)
- (outline-window outline-window))
- (declare (optimize speed))
- ;;
- (unless (f-expanded-outline-node outline-node)
- ;;
- ;; Expand outline-node and insert lines for each of its children,
- ;; respecting each's indent level.
- ;;
- (expand-outline-node outline-node (fn-l-children-outline-window outline-window))
- (setf (l-outline-node-ow outline-window)
- (append (l-outline-node-ow outline-window)
- (l-on-children-outline-node outline-node)))
- (update-arrow-outline-node-ow outline-node outline-window)
- (loop with outline-node-prev = outline-node
- for outline-node-child in
- (if (f-sort-children-outline-window outline-window)
- (sort (copy-list (l-on-children-outline-node outline-node))
- #'string<
- :key #'(lambda (outline-node)
- (funcall (fn-str-object-outline-window outline-window)
- (object-outline-node outline-node))))
- (l-on-children-outline-node outline-node))
- for buffer-prev = (mark-line-start-outline-node outline-node-prev)
- for int-line-start = (buffer-line-start
- buffer-prev
- (buffer-position buffer-prev)1)
- do (progn
- (insert-text-outline-node-ow outline-node-child outline-window
- int-line-start)
- (setf outline-node-prev outline-node-child)))
- (fred-update outline-window)))
-
-
- (defgeneric collapse-outline-node-ow (outline-node outline-window)
- (:documentation "Expands outline-node in outline-window, and updates the
- display."))
-
-
- (defmethod collapse-outline-node-ow ((outline-node outline-node)
- (outline-window outline-window))
- (declare (optimize speed))
- ;;
- (when (f-expanded-outline-node outline-node)
- ;;
- ;; Collapse outline-node and collapse and delete each of its children.
- ;;
- (setf (l-outline-node-ow outline-window)
- (set-difference (l-outline-node-ow outline-window)
- (l-on-children-outline-node outline-node)))
- (collapse-outline-node outline-node)
- (update-arrow-outline-node-ow outline-node outline-window)
- (loop for outline-node-child in (l-on-children-outline-node outline-node)
- do (progn
- (collapse-outline-node-ow outline-node-child outline-window)
- (delete-text-outline-node-ow outline-node-child outline-window)))
- (fred-update outline-window)))
-
-
- ;;;================================================================
- ;;; Define support functions.
- ;;;================================================================
-
- (defun str-format-object (object)
- "Returns object's ~A format string."
- (declare (optimize speed))
- ;;
- (format nil "~A" object))
-
-
- (defmethod object-node-from-int-line-start ((outline-window outline-window)
- (int-line-start fixnum))
- "Returns the outline-node whose mark-line-start-outline-node equals
- int-line-start ."
- (declare (optimize speed))
- ;;
- (loop for outline-node in (l-outline-node-ow outline-window)
- for buffer = (mark-line-start-outline-node outline-node)
- for int-line-start-node = (buffer-position buffer)
- when (= int-line-start-node int-line-start)
- do (return outline-node) ;exits before finally clause, if found
- finally (return nil)))
-
-
- (defmethod insert-text-outline-node-ow ((outline-node outline-node)
- (outline-window outline-window)
- (int-line-start fixnum))
- "Inserts in outline-window's fred-buffer outline-node's text, and sets
- outline-node's mark-line-start-outline-node to be the line just inserted
- at."
- (declare (optimize speed))
- ;;
- (let* ((buffer (fred-buffer outline-window))
- (str-object (funcall (fn-str-object-outline-window outline-window)
- (object-outline-node outline-node)))
- (int-indent (int-indent-outline-window outline-window))
- (int-level (int-level-outline-node outline-node))
- (str-object-indented
- (concatenate 'string
- (make-string (* int-indent (1+ int-level))
- :initial-element #\Space)
- str-object
- (string #\Return))))
- (ccl::%buffer-set-read-only buffer nil)
- (buffer-insert buffer str-object-indented int-line-start)
- ;; Make the mark *after* the insert so that it doesn't move on us.
- (setf (mark-line-start-outline-node outline-node)
- (make-mark (fred-buffer outline-window) int-line-start))
- (ccl::%buffer-set-read-only buffer t)
- (update-arrow-outline-node-ow outline-node outline-window)))
-
-
- (defmethod delete-text-outline-node-ow ((outline-node outline-node)
- (outline-window outline-window))
- "Deletes from outline-window's fred-buffer outline-node's text."
- (declare (optimize speed))
- ;;
- (let* ((buffer (mark-line-start-outline-node outline-node))
- (int-start (buffer-position buffer))
- (int-end (1+ (buffer-line-end buffer)))) ;1+ gets the #\Return
- (ccl::%buffer-set-read-only buffer nil)
- (buffer-delete (fred-buffer outline-window) int-start int-end)
- (ccl::%buffer-set-read-only buffer t)))
-
-
- ;;;================================================================
- ;;; Define functions for handling arrows.
- ;;;================================================================
-
- (defmethod update-arrow-outline-node-ow ((outline-node outline-node)
- (outline-window outline-window))
- "Inserts the proper arrow icon at column 1 of the outline-node's line."
- (declare (optimize speed))
- ;;
- ;; Compute children so we know if outline-node has any. We must know this
- ;; because no arrows are drawn for childless nodes.
- ;;
- (compute-children-if-necessary outline-node
- (fn-l-children-outline-window outline-window))
- ;;
- (let* ((font-spec-old (view-font outline-window))
- (font-spec-arrow font-spec-old)
- ;(font-spec-arrow '("Symbol" :outline))
- (buffer (mark-line-start-outline-node outline-node))
- (int-buffer-pos (buffer-position buffer))
- (string-arrow (cond ((null (l-on-children-outline-node outline-node))
- " ")
- ;; Following two arrows are in the Symbol font:
- ((f-expanded-outline-node outline-node)
- ;"Ø" ;symbol
- "+")
- (t
- ;"Æ" ;symbol
- "-"))))
- (ccl::%buffer-set-read-only buffer nil)
- (buffer-delete buffer int-buffer-pos (1+ int-buffer-pos))
- (buffer-set-font-spec buffer font-spec-arrow)
- ;; This insert increments buffer's position so we correct it back afterwards:
- (buffer-insert buffer string-arrow)
- (set-mark buffer int-buffer-pos)
- ;;
- (buffer-set-font-spec buffer font-spec-old)
- (ccl::%buffer-set-read-only buffer t)
- (fred-update outline-window)))
-
-
- (defmethod f-on-arrow-pt ((outline-window outline-window) (pt-where fixnum))
- "Returns the outline-node clicked on if pt-where is on its corresponding
- arrow. Returns nil otherwise."
- (declare (optimize speed))
- ;;
- (let* ((int-buffer-pos-click (fred-point-position outline-window pt-where))
- (buffer (fred-buffer outline-window))
- (int-line-start (buffer-line-start buffer int-buffer-pos-click))
- (clicked-on-object-node (object-node-from-int-line-start
- outline-window int-line-start)))
- (if (and clicked-on-object-node
- ;; The or allows for slop.
- (or (= int-buffer-pos-click int-line-start)
- (= int-buffer-pos-click (1+ int-line-start))))
- clicked-on-object-node
- nil)))
-
-
- ;;;
- ;;; Done.
- ;;;
-
- (provide "OUTLINE-WINDOW")
-
-
- #| ;;; Define some testing functions.
-
- (defun test-ow1 ()
- "Makes an outline-window that displays this simple symbol hierarchy:
- a - b - d
- \
- c - e
- \
- f "
- (declare (optimize speed))
- ;;
- (make-instance 'outline-window
- :window-title "Simple Outline"
- :f-sort-children nil
- :root-object 'a
- :fn-l-children #'(lambda (sym-object)
- (case sym-object
- (a '(b c))
- (b '(d))
- (c '(e f))
- (t ())))))
-
-
- ;;;
- ;;; Define a class for viewing CLOS instance hierarchies.
- ;;;
-
- (defclass class-outline-window (outline-window)
- () ;no new slots
- (:documentation "A subclass of outline-window whose instances show the
- CLOS class hierarchy, print classes nicely, and print documentation on
- double-clicks.")
- (:default-initargs
- :view-size #@(256 313)
- :int-indent 3
- :fn-l-children #'class-direct-subclasses
- :fn-str-object #'(lambda (class)
- (substitute
- #\Space #\-
- (format nil "~:(~A~)" (class-name class))))))
-
-
- (defmethod initialize-instance :after ((class-outline-window class-outline-window)
- &key root-object)
- (declare (optimize speed))
- ;;
- (set-window-title class-outline-window
- (format nil "~:(~A~) Hierarchy" (class-name root-object))))
-
-
- (defmethod view-click-event-handler :after ((class-outline-window class-outline-window)
- (pt-where fixnum))
- "Prints the selected object's documentation if it's non-nil and there
- was a double-click. Edits the definition if the command and option keys
- were down."
- ;;
- (let* ((f-outline-node-sel (f-outline-node-selected-ow class-outline-window))
- (class (and f-outline-node-sel
- (object-outline-node f-outline-node-sel)))
- (str-doc (and class
- (or (documentation class)
- (documentation (class-name class) 'class)))))
- (when (and class
- (double-click-p)
- str-doc)
- (format t "~&~A: ~A" class (substitute #\Space #\Return str-doc)))
- (when (and class
- (command-key-p)
- (option-key-p))
- (edit-definition (class-name class)))))
-
-
- (defun test-ow2 ()
- "Makes an outline-window that displays the CLOS hierarchy. Double click
- on 'Outline Node' to get documentation (few other classes are documented)."
- (declare (optimize speed))
- ;;
- (make-instance 'class-outline-window
- :root-object (find-class 'standard-object)))
-
-
- (defun test-ow3 ()
- "Makes an outline-window that displays MCL's simple-view hierarchy."
- (declare (optimize speed))
- ;;
- (make-instance 'class-outline-window
- :root-object (find-class 'simple-view)))
-
- |#